Get Started

Catalyst API - Get Started Guide

Overview

The Catalyst API enables dealers to access the data necessary to process the entire Sunnova sales process.

This guide covers;

  1. How to get authorized access to the Catalyst API
  2. How to complete the first step in the Sunnova sales process (create a lead)

resources/Screenshot%202024-09-26%20at%203.29.23%20PM-c06df3b0-dd51-4bc1-806c-49c1cab18538.png

Documentation for the additional steps in the Sunnova Sales process can be found here.

Integrating with Catalyst API

Before getting started with API integration, you’ll need to have an account set up within our system. Account creation is done as part of the Sunnova Dealer onboarding process, so if you’re unsure if you have an existing account, reach out to your Dealer Sales Manager (DSM) to confirm.

Authorization

Once your account is set up, you will need to get authorized access to the Catalyst API.

Although Sunnova strongly encourages JWT authentication, the Catalyst API additionally supports basic authentication. It should be noted that the /contract and /credit endpoints are not available for users on basic authentication.

JWT Authorization

For optimal security, Sunnova strongly encourages the use of server-to-server JWT authentication.

API Keys

JWT authentication requires “API keys” to be generated for your specific account and environment which consist of;

  1. Java key store certificate
  2. Consumer key
  3. Password
  4. Server details

Your Dealer Sales Manager (DSM) will coordinate getting these API keys generated for you and will have them sent to your technical contact(s) identified during onboarding. Contact your DSM if you have not yet received your API keys or would like to submit a request to have them generated.

Generating Your JSON Web Token (JWT)

Once you’ve received your API keys, you will need to generate a JSON web token (JWT) using RSA SHA256 to call our /JWTAuthorization endpoint.

JSON Web Tokens consist of three parts separated by dots (.), which are:

Header3

Payload

Signature

Therefore, a JWT typically looks like the following; header.payload.signature

The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as RS256.

For example:

resources/Screenshot%202024-09-26%20at%203.34.04%20PM-9d1bc08c-08a0-4479-9882-26d5760fd25d.png

Then, this JSON needs to be Base64Url encoded to form the first part of the JWT.

Payload

The second part of the token is the payload, which contains the claims. Claims are statements about an entity and are iss (issuer), sub (subject), aud (audience), and exp (expiration time). The values for each will be provided to you, along with your .jks file, when your API keys are issued.

resources/Screenshot%202024-09-26%20at%203.35.25%20PM-2cb00b0b-a77b-4e9b-8765-fbf3de5b763b.png

The payload needs to be Base64Url encoded to form the second part of the JWT.

Signature

To create the signature, you have to take the encoded header, the encoded payload, secret/password and public key certificate that was provided by Sunnova, and sign that with RSA SHA256 (SHA256withRSA) algorithm.

resources/Screenshot%202024-09-26%20at%203.37.47%20PM-c502fc86-529d-4d7f-9004-46672ea91384.png

Putting It All Together

The output from the previous step is three Base64-URL strings separated by dots that can be easily passed to the POST /JWTAuthorization endpoint to generate the access token.

Below is the sample request and response payload;

resources/Screenshot%202024-09-26%20at%203.39.29%20PM-057c0002-cb61-4c7d-a68b-f2b170b290fb.png

Note: Python can also be used to generate the JWT assertion necessary to authenticate.

Below is the Python Program that can be used, which would need to be updated with the appropriate files, claims, and other details necessary to generate the JWT assertion.

import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates


def generate_jwt_assertion(pkcs12_file, pkcs12_password, jwt_payload, jwt_secret):
# Load the PKCS12 file and extract the private key and certificate
with open(pkcs12_file, 'rb') as file:
pkcs12_data = file.read()


private_key, certificate, _ = load_key_and_certificates(pkcs12_data, pkcs12_password.encode('utf-8'), default_backend())


# Generate the JWT assertion
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
private_key_str = private_key_pem.decode('utf-8')


certificate_pem = certificate.public_bytes(serialization.Encoding.PEM).decode('utf-8')
jwt_payload['aud'] = '<a href="https://sunnova--staging.sandbox.my.site.com/" target="_blank">https://sunnova--staging.sandbox.my.site.com</a>'
jwt_payload['iss'] = '3MVG91Cqi3bGIrUM7kDqHzSyU6hFuWndt0OfAoTeEvjFeAFCSv1nz_Q8Hsj9Q3Rwu2e8cv0hHDqPP0_OTj'


jwt_assertion = jwt.encode(jwt_payload, private_key_str, algorithm='RS256', headers={'x5c': [certificate_pem]})


return jwt_assertion


# Example usage
pkcs12_file = 'StagingSunnova.p12'   #Path where .p12 file is installed/stored.
pkcs12_password = 'Sunnova123'      # Private key Provided by Sunnova
jwt_payload = {'sub': '<a href="mailto:srini.yeduru@sunnova.com.staging" target="_blank">srini.yeduru@sunnova.com.staging</a>', 'exp': 1735689600} #Username of the sales Rep and Expiry time.
jwt_secret = 'Sunnova123'       # Private key provided by Sunnova.

jwt_assertion = generate_jwt_assertion(pkcs12_file, pkcs12_password, jwt_payload, jwt_secret)
print(jwt_assertion)
Basic Authentication

While we strongly recommend JWT authentication when integrating with our API for optimal security, Catalyst API also supports basic authentication via a Base64 encrypted Basic Authentication header. It should be noted that the /contract and /credit endpoints are not available for users on basic authentication. The only authentication method supported by our webhooks is basic authentication.

For this basic authentication, you would perform a GET request to our /authentication endpoint to provide a username and password and receive an access token. For the authorization to be meaningful, you should handle the decryption on your end and verify the credentials match. The access token you receive should then be used as a bearer token in all API requests going forward.

Below is an example request for an access token using Basic Authentication via the Postman application for Sunnova’s Test environment- endpoint:

resources/Screenshot%202024-09-26%20at%203.41.36%20PM-a6bf9f34-b573-472c-81fc-d78d02f2bad9.png

Creating a Lead

Once you’ve successfully established an authenticated connection to Catalyst API, you’re now ready to create a lead.

POST /leads

The POST /services/v1.0/leads call creates a new Lead. It will also create a Lead Person to store the contact details of the Lead. In other words, think of the Lead as representing a property address and the Lead Person as representing the Contact at that address.

Upon creation of the new lead, title information is added automatically and asynchronously through Sunnova’s 3rd party title partner, Black Knight. No additional call following the /leads POST is necessary for this to occur. Any additional people identified on title will automatically be created as Contacts as part of this process as well.

The following fields of the request payload are mandatory fields. If they are not populated when the request payload is sent, the API call will respond with an error.

resources/Screenshot%202024-09-26%20at%203.44.08%20PM-d8ae82dc-8b28-4edd-9637-43863319ac43.png

In addition to these mandatory fields, Content-type and authorization headers are also required, with the expected value of a string.

Request Body Example

Lead

{
    "FirstName": "Ronald",
    "LastName": "Smith",
    "Middle_Name": "",
    "Suffix": "Sr.",
    "Email": "ronald.smith@sunnova.com",
    "Preferred_Contact_Method": "Email",
    "Preferred_Language": "English",
    "Phone": {
        "Number": "7135551234",
        "Type": "Home"
    },
    "Address": {
        "Street": "72 Stepstone Hill Rd",
        "City": "Guilford",
        "State": "CT",
        "PostalCode": "06437",
        "Country": "United States"
    },
    "external_id": "api_Lead",
    "LeadSource": "Dealer API"
}
Response Body Example (success)

Status: 201 Created

[
    {
        "lead": {
            "FirstName": "Ronald",
            "LastName": "Smith",
            "Middle_Name": "",
            "Suffix": "Sr.",
            "Email": "ronald.smith@sunnova.com",
            "Address_Validated": true,
            "ContractType": null,
            "Continue_without_Title_Validation": false,
            "Phone": {
                "Number": "7135551234",
                "Type": "Home"
            },
            "Address": {
                "Street": "72 Stepstone Hill Rd",
                "City": "Guilford",
                "State": "CT",
                "PostalCode": "06437",
                "Country": "US",
                "County": "New Haven County",
                "Latitude": 41.320647,
                "Longitude": -72.694221,
                "Elevation": 47.430019,
                "Place_Id": "ChIJDwK0Irss5okRkrkvug4QtKw"
            },
            "Lead_Detail": null,
            "LeadSource": "Dealer API",
            "ContactId": "a8kVB0000009qk9YAA",
            "External_Id": "api_Lead",
            "Preferred_Contact_Method": "Email",
            "Preferred_Language": "English",
            "Web_Tracking": null,
            "Retail_Rep_Name": null,
            "Id": "00QVB000003IA412AG",
            "Geocoding": {
                "Status": "VALID",
                "Message": "The address provided was successfully geocoded. The lead record was updated accordingly.",
                "Results": [
                    {
                        "Street": "72 Stepstone Hill Rd",
                        "City": "Guilford",
                        "State": "CT",
                        "PostalCode": "06437",
                        "Country": "US",
                        "County": "New Haven County",
                        "Latitude": 41.320647,
                        "Longitude": -72.694221,
                        "Elevation": 47.430019,
                        "Place_Id": "ChIJDwK0Irss5okRkrkvug4QtKw"
                    }
                ]
            }
        }
    }
]
Response Body Example (error)

Status: 400

The most common causes of a 400 error are missing mandatory fields or headers in the request payload, the user executing the request not having the appropriate permissions, or an expired token.

{
    "message": "Connectivity Issue.  Your token may have expired.  Please regenerate the token.",
    "info": "https://anypoint.mulesoft.com/exchange/portals/sunnova-energy-corporation-7/",
    "reason": "Invalid Login",
    "code": 401
}
Missing mandatory fields or headers error

If any of the mandatory fields outlined in the table at the beginning of this ‘Creating a Lead’ section are not populated when the request payload is sent, the API call will respond with an error. In addition to these mandatory fields, Content-type and authorization headers are also required, with the expected value of a string.

If you receive an authentication error, expired token error, or invalid session error, your next step is to generate a new JWT assertion and to get a new token by calling the /JWTAuthorization endpoint using that assertion. For basic authorization, Use GET /services/v1.0/authentication.

GET /{LeadID}

Once you’ve been actively creating Leads, the GET /services/v1.0/leads/{LeadID} call will return a list of all Leads created for the current user. Keep in mind that pagination is enforced, and the maximum and default limit parameter is 200.

Also, query parameters can be passed to filter records, with any Lead field visible by your user eligible for filtering. Sorting or ordering by visible fields is not currently supported. The current available list of Lead fields visible by the user is returned by the POST /services/v1.0/leads endpoint.

Example

GET https://apitest.sunnova.com/services/v1.0/leads?limit=1&offset=0

Rules to keep in mind

Equals (=) is the only operator supported at this time.

Use Limit to specify the maximum number of rows to return

Use OFFSET to specify the starting row offset into the result set returned by your query. Because the offset calculation is done on the server and only the result subset is returned, using OFFSET is more efficient than retrieving the full result set and then filtering the results locally.

[
    {
         "Leads": {
             "Name": "Ronald Smith",
             "FirstName": "Ronald",
             "LastName": "Smith",
             "Address": {
                 "Street": "72 Stepstone Hill Rd",
                 "City": "Guilford",
                 "State": "CT",
                 "PostalCode": "06437",
                 "Country": "US",
                 "County": "New Haven County",
                 "Latitude": 41.320647,
                 "Longitude": -72.694221,
                 "Elevation": 47.430019,
                 "Place_Id": "ChIJDwK0Irss5okRkrkvug4QtKw"
            },
            "Aurora_AutoRoof_Project_Id": null,
            "LeadSource": "Dealer API",
            "Lead_Detail": null,
            "Address_Validated": true,
            "Continue_without_Title_Validation": false,
            "Contract_Type": "PPA-EZ",
            "CreatedDate": "2024-03-11T15:20:02.000Z",
            "Days_Since_Credit_Run": 0.0,
            "Dealer_Account_Name": "Trinity Solar",
            "External_Id": "api_Lead",
            "Id": "00QVB000003IA412AG",
            "Status": "Open",
            "Lead_Sub_Status": null,
            "Lead_Sub_Status_Detail": null,
            "Lead_System_Exists": true,
            "Installation_Dealer_Name": null,
            "Is_Change_Order": false,
            "Is_Converted": false,
            "Title_Verified": true,
            "Lead_Owner": "005E0000007GHklIAG",
            "Retail_Rep_Name": null,
            "Salesperson_Id": "003E000000qVHDqIAO",
            "Salesperson_Name": "Ken Ebbert",
            "LastModifiedDate": "2024-03-11T15:56:19.000Z",
            "WebTracking": null,
            "UTM_Name": null,
            "UTM_Content": null,
            "UTM_Source": null,
            "Appointment": null
        },
        "Autoroof_Customer_Comments": null
    }
]

API SUPPORT

If you have a specific issue that requires support, please send an email to apisupport@sunnova.com with the following;

For API Troubleshooting

  1. Summary of issue
  2. Affected endpoint(s) and environment(s)
  3. Request and response body in question
  4. Integration user that executed the request(s)
  5. Expected vs. observed behavior
  6. Example request(s) or screenshots

For Webhook Troubleshooting

  1. Summary of issue
  2. Affected webhook(s) and environment(s)
  3. Expected vs. observed behavior
  4. Example payload(s) or screenshots
API Support Turnaround Time Expectation
  • Initial triage within 5 business days, based on urgency/priority
  • Follow-up response no later than an additional 7 business days